home *** CD-ROM | disk | FTP | other *** search
/ Mac Easy 2010 May / Mac Life Ubuntu.iso / casper / filesystem.squashfs / usr / share / pyshared / usbcreator / gtk_frontend.py < prev    next >
Encoding:
Python Source  |  2009-04-17  |  19.5 KB  |  501 lines

  1. # Copyright (C) 2008 Canonical Ltd.
  2.  
  3. # This program is free software: you can redistribute it and/or modify
  4. # it under the terms of the GNU General Public License version 3,
  5. # as published by the Free Software Foundation.
  6. #
  7. # This program is distributed in the hope that it will be useful,
  8. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  10. # GNU General Public License for more details.
  11. #
  12. # You should have received a copy of the GNU General Public License
  13. # along with this program.  If not, see <http://www.gnu.org/licenses/>.
  14.  
  15. import sys
  16. import os
  17.  
  18. from usbcreator.backend import Backend
  19. import gettext
  20. import locale
  21. import pygtk
  22. import gtk.glade
  23. import gobject
  24. import dbus
  25. import gnomevfs
  26.  
  27. MIN_PERSIST = 128 * 1024 * 1024 # The minimal size, in bytes, that a persistence file can be.
  28. LOCALEDIR = "/usr/share/locale"
  29.  
  30. #class GtkFrontend(Frontend):
  31. class GtkFrontend:
  32.     def __init__(self,iso=None,persistent=True):
  33.         self.YES, self.MAYBE, self.NO = range(3)
  34.  
  35.         locale.setlocale(locale.LC_ALL, '')
  36.         for module in gtk.glade, gettext:
  37.             module.bindtextdomain('usbcreator', LOCALEDIR)
  38.             module.textdomain('usbcreator')
  39.  
  40.         import __builtin__
  41.         __builtin__._ = gettext.gettext
  42.  
  43.         self.all_widgets = set()
  44.  
  45.         self.glade = gtk.glade.XML('/usr/share/usb-creator/usbcreator.glade')
  46.         for widget in self.glade.get_widget_prefix(""):
  47.             # Taken from ubiquity:
  48.             # We generally want labels to be selectable so that people can
  49.             # easily report problems in them
  50.             # (https://launchpad.net/bugs/41618), but GTK+ likes to put
  51.             # selectable labels in the focus chain, and I can't seem to turn
  52.             # this off in glade and have it stick. Accordingly, make sure
  53.             # labels are unfocusable here.
  54.             if isinstance(widget, gtk.Label):
  55.                 widget.set_property('can-focus', False)
  56.             self.all_widgets.add(widget)
  57.             setattr(self, widget.get_name(), widget)
  58.  
  59.         self.backend = Backend(self)
  60.  
  61.         gtk.window_set_default_icon_from_file('/usr/share/pixmaps/usb-creator.png')
  62.         self.glade.signal_autoconnect(self)
  63.         self.cancelbutton.connect('clicked', lambda x: self.warning_dialog.hide())
  64.         self.exitbutton.connect('clicked', lambda x: self.abort())
  65.         self.progress_cancel_button.connect('clicked', lambda x: self.warning_dialog.show())
  66.         def format_value(scale, value):
  67.             return format_size(value)
  68.         self.persist_value.set_adjustment(
  69.             gtk.Adjustment(0, 0, 100, 1, 10, 0))
  70.         self.persist_value.connect('format-value', format_value)
  71.  
  72.         self.setup_source()
  73.         self.setup_dest()
  74.         m = self.dest_treeview.get_model()
  75.         self.update_row_state(m, None)
  76.         self.persist_vbox.set_sensitive(False)
  77.         self.button_install.set_sensitive(False)
  78.         self.backend.detect_devices()
  79.         self.window.show()
  80.         
  81.         if iso is not None:
  82.             self.backend.mount_iso(iso)
  83.         
  84.         if not persistent:
  85.             self.persist_disabled.set_active(True)
  86.  
  87.         gtk.main()
  88.  
  89.     def abort(self, *args):
  90.         self.backend.shutdown()
  91.         sys.exit(0)
  92.  
  93.     def quit(self, *args):
  94.         self.backend.quit()
  95.         sys.exit(0)
  96.  
  97.     def failed(self, title=None):
  98.         self.backend.shutdown()
  99.         self.warning_dialog.hide()
  100.         self.install_window.hide()
  101.         if title:
  102.             self.failed_dialog_label.set_text(title)
  103.             self.backend.log('Install failed: ' + title)
  104.         else:
  105.             self.backend.log('Install failed.')
  106.         self.failed_dialog.run()
  107.         sys.exit(1)
  108.     
  109.     def finished(self, *args):
  110.         self.warning_dialog.hide()
  111.         self.install_window.hide()
  112.         self.finished_dialog.run()
  113.         sys.exit(0)
  114.  
  115.     def notify(self, message):
  116.         dialog = gtk.MessageDialog(None, 0, gtk.MESSAGE_WARNING,
  117.             gtk.BUTTONS_CLOSE, message)
  118.         dialog.run()
  119.         dialog.destroy()
  120.  
  121.     def format_dest_clicked(self, *args):
  122.         model, iterator = self.dest_treeview.get_selection().get_selected()
  123.         if not iterator:
  124.             return
  125.         disk = model[iterator][0]
  126.         self.backend.format_device(disk)
  127.  
  128.     def open_dest_folder(self, *args):
  129.         # TODO: This should really call whatever GNOME does when you click on
  130.         # the device in the places menu, so the device gets mounted if it isn't
  131.         # already.
  132.         # TODO: Should use something neutral as XFCE could also use the GTK
  133.         # frontend.
  134.         model, iterator = self.dest_treeview.get_selection().get_selected()
  135.         if not iterator:
  136.             return
  137.         disk = model[iterator][0]
  138.         mp = self.backend.devices[disk]['mountpoint']
  139.         if mp:
  140.             cmd = ['gnome-open', mp]
  141.             from usbcreator.backend import popen
  142.             popen(cmd)
  143.  
  144.     def add_source(self, source):
  145.         ni = self.source_treeview.get_model().append([source])
  146.         sel = self.source_treeview.get_selection()
  147.         m, i = sel.get_selected()
  148.         if not i:
  149.             sel.select_iter(ni)
  150.  
  151.     def device_removed(self, d, source):
  152.         '''The backend has removed a device from its list and the frontend now
  153.         needs to do the same.
  154.  
  155.         Keyword arguments:
  156.         d      -- the key (a udi string) of the item to delete.
  157.         source -- if True, then the frontend needs to remove a source device
  158.                   (CD), otherwise it needs to remove a destination device (USB
  159.                   drive).
  160.         '''
  161.         # TODO: Maybe split this in half?
  162.         to_delete = None
  163.         if source:
  164.             m = self.source_treeview.get_model()
  165.         else:
  166.             m = self.dest_treeview.get_model()
  167.         iterator = m.get_iter_first()
  168.         while iterator is not None:
  169.             if m.get_value(iterator, 0) == d:
  170.                 to_delete = iterator
  171.                 self.backend.log('deleting %s from the ui' % d)
  172.             iterator = m.iter_next(iterator)
  173.         if to_delete is not None:
  174.             m.remove(to_delete)
  175.         if len(m) == 0:
  176.             self.backend.log('device removed and none left.  source = %s' % str(source))
  177.             self.persist_vbox.set_sensitive(False)
  178.             self.button_install.set_sensitive(False)
  179.  
  180.     def update_all_rows(self, selection):
  181.         m = self.dest_treeview.get_model()
  182.         iterator = m.get_iter_first()
  183.         while iterator is not None:
  184.             self.update_row_state(m, iterator)
  185.             iterator = m.iter_next(iterator)
  186.  
  187.         # TODO: Disabled for now, re-evaluate as testing allows.  Initial
  188.         # testing looks OK.
  189.         #sel = self.dest_treeview.get_selection()
  190.         #self.dest_selection_changed(sel)
  191.  
  192.     def get_gnome_drive(self, udi):
  193.         monitor = gnomevfs.VolumeMonitor()
  194.         for drive in monitor.get_connected_drives():
  195.             if drive.get_hal_udi() == udi:
  196.                 return drive
  197.  
  198.     def setup_source(self):
  199.         def column_data_func(column, cell, model, iterator, pos):
  200.             val = model[iterator][0]
  201.             dev = self.backend.cds[val]
  202.             if pos == 0:
  203.                 if dev['udi']:
  204.                     drive = self.get_gnome_drive( dev['udi'] )
  205.                     if drive:
  206.                         cell.set_property('text', drive.get_display_name())
  207.                     else:
  208.                         cell.set_property('text', dev['mountpoint'])
  209.                 else:
  210.                     cell.set_property('text', dev['filename'])
  211.             elif pos == 1:
  212.                 cell.set_property('text', dev['label'])
  213.             elif pos == 2:
  214.                 cell.set_property('text', format_size(dev['size']))
  215.  
  216.         def pixbuf_data_func(column, cell, model, iterator):
  217.             dev = self.backend.cds[model[iterator][0]]
  218.             drive = self.get_gnome_drive( dev['udi'] )
  219.             if drive:
  220.                 cell.set_property('icon-name', drive.get_icon())
  221.             else:
  222.                 cell.set_property('stock-id', None)
  223.  
  224.  
  225.         # TODO: Drag and drop support.  Should be over the entire window.
  226.         list_store = gtk.ListStore(str)
  227.         self.source_treeview.set_model(list_store)
  228.  
  229.         cell_name = gtk.CellRendererText()
  230.         cell_pixbuf = gtk.CellRendererPixbuf()
  231.         column_name = gtk.TreeViewColumn(_('CD-Drive/Image'))
  232.         column_name.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE)
  233.         column_name.pack_start(cell_pixbuf, expand=False)
  234.         column_name.pack_start(cell_name, expand=True)
  235.         self.source_treeview.append_column(column_name)
  236.         column_name.set_cell_data_func(cell_name, column_data_func, 0)
  237.         column_name.set_cell_data_func(cell_pixbuf, pixbuf_data_func)
  238.  
  239.         cell_version = gtk.CellRendererText()
  240.         column_name = gtk.TreeViewColumn(_('OS Version'), cell_version)
  241.         column_name.set_cell_data_func(cell_version, column_data_func, 1)
  242.         column_name.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE)
  243.         self.source_treeview.append_column(column_name)
  244.  
  245.         cell_size = gtk.CellRendererText()
  246.         column_name = gtk.TreeViewColumn(_('Size'), cell_size)
  247.         column_name.set_cell_data_func(cell_size, column_data_func, 2)
  248.         column_name.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE)
  249.         self.source_treeview.append_column(column_name)
  250.  
  251.         selection = self.source_treeview.get_selection()
  252.         selection.connect('changed', self.update_all_rows)
  253.  
  254.     def select_iso(self, *args):
  255.         filename = ''
  256.         chooser = gtk.FileChooserDialog(title=None,action=gtk.FILE_CHOOSER_ACTION_OPEN,
  257.             buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_OPEN,gtk.RESPONSE_OK))
  258.         filter = gtk.FileFilter()
  259.         filter.add_pattern('*.iso')
  260.         # TODO: i18n
  261.         filter.set_name(_('ISO Files'))
  262.         chooser.add_filter(filter)
  263.         if 'SUDO_USER' in os.environ:
  264.             folder = os.path.expanduser('~' + os.environ['SUDO_USER'])
  265.         else:
  266.             folder = os.path.expanduser('~')
  267.         chooser.set_current_folder(folder)
  268.         response = chooser.run()
  269.         failed = False
  270.         if response == gtk.RESPONSE_OK:
  271.             filename = chooser.get_filename()
  272.             self.backend.mount_iso(filename)
  273.         chooser.destroy()
  274.  
  275.     def add_dest(self, dest):
  276.         self.backend.log('adding: %s' % dest)
  277.         ni = self.dest_treeview.get_model().append([dest, self.YES])
  278.         sel = self.dest_treeview.get_selection()
  279.         m, i = sel.get_selected()
  280.         if not i:
  281.             sel.select_iter(ni)
  282.  
  283.     def update_row_state(self, model, iterator):
  284.         m, i = self.source_treeview.get_selection().get_selected()
  285.         if not i:
  286.             self.source_status.set_text(_('Please insert a CD or select \'Other...\'.'))
  287.         else:
  288.             self.source_status.set_text('')
  289.         if not iterator:
  290.             self.open_dest.hide()
  291.             self.format_dest.hide()
  292.             self.dest_status.set_text(_('Please insert a USB stick.'))
  293.             iterator = model.get_iter_first()
  294.             while iterator is not None:
  295.                 model[iterator][1] = self.YES
  296.                 iterator = model.iter_next(iterator)
  297.         else:
  298.             self.dest_status.set_text('')
  299.         if not i or not iterator:
  300.             # There's no selection, which means one of the treeviews is empty.
  301.             return
  302.  
  303.         cd = self.backend.cds[m[i][0]]
  304.         disk = model[iterator][0]
  305.         disk = self.backend.devices[disk]
  306.  
  307.         # Disk
  308.         if not disk['fstype']:
  309.             if cd['size'] > disk['capacity']:
  310.                 model[iterator][1] = self.NO
  311.             else:
  312.                 model[iterator][1] = self.MAYBE
  313.         # Partition
  314.         else:
  315.             if cd['size'] < disk['free']:
  316.                 model[iterator][1] = self.YES
  317.             elif cd['size'] > disk['capacity']:
  318.                 model[iterator][1] = self.NO
  319.             else:
  320.                 model[iterator][1] = self.MAYBE
  321.  
  322.         if self.dest_treeview.get_selection().iter_is_selected(iterator):
  323.             self.backend.log('updating dest_status as part of update_row_state')
  324.             self.open_dest.hide()
  325.             self.format_dest.hide()
  326.             self.button_install.set_sensitive(False)
  327.             # Disk
  328.             if not disk['fstype']:
  329.                 if cd['size'] > disk['capacity']:
  330.                     self.dest_status.set_text(_('%s is too small for %s.') % \
  331.                         (disk['device'], cd['label']))
  332.                 else:
  333.                     self.dest_status.set_text(_('%s needs to be formatted.') % \
  334.                         disk['device'])
  335.                     self.format_dest.show()
  336.             # Partition
  337.             else:
  338.                 if cd['size'] < disk['free']:
  339.                     self.dest_status.set_text(
  340.                         _('%s has enough free space for %s.') % \
  341.                         (disk['device'], cd['label']))
  342.                     self.button_install.set_sensitive(True)
  343.                     self.persist_vbox.set_sensitive(True)
  344.                     persist_max = disk['free'] - cd['size']
  345.                     if persist_max > MIN_PERSIST:
  346.                         self.persist_vbox.set_sensitive(True)
  347.                         self.persist_enabled_vbox.set_sensitive(True)
  348.                         self.persist_value.set_range(MIN_PERSIST, persist_max)
  349.                     else:
  350.                         self.persist_enabled_vbox.set_sensitive(False)
  351.                         self.persist_disabled.set_active(True)
  352.                 elif cd['size'] > disk['capacity']:
  353.                     self.dest_status.set_text(_('%s is too small for %s.') % \
  354.                         (disk['device'], cd['label']))
  355.                 else:
  356.                     needed = cd['size'] - disk['free']
  357.                     needed = format_size(needed)
  358.                     # TODO: Can we size the window at startup to fix this
  359.                     # string and the button?
  360.                     self.dest_status.set_text(
  361.                         _('%s is too full to fit %s (%s more needed).') % \
  362.                         (disk['device'], cd['label'], needed))
  363.                     self.open_dest.show()
  364.  
  365.     def update_dest_row(self, device):
  366.         m = self.dest_treeview.get_model()
  367.         iterator = m.get_iter_first()
  368.         while iterator is not None:
  369.             if m.get_value(iterator, 0) == device:
  370.                 self.update_row_state(m, iterator)
  371.                 m.row_changed(m.get_path(iterator), iterator)
  372.                 break
  373.             iterator = m.iter_next(iterator)
  374.  
  375.     def dest_selection_changed(self, selection):
  376.         model, iterator = selection.get_selected()
  377.         self.update_row_state(model, iterator)
  378.  
  379.     def setup_dest(self):
  380.         def column_data_func(column, cell, model, iterator, pos):
  381.             val = model[iterator][0]
  382.             dev = self.backend.devices[val]
  383.             drive = self.get_gnome_drive( dev['udi'] )
  384.             if pos == 0:
  385.                 if drive:
  386.                     cell.set_property('text', drive.get_display_name() )
  387.                 else:
  388.                     cell.set_property('text', dev['device'])
  389.             elif pos == 1:
  390.                 cell.set_property('text', dev['label'])
  391.             elif pos == 2:
  392.                 cell.set_property('text', format_size(dev['capacity']))
  393.             elif pos == 3:
  394.                 cell.set_property('text', format_size(dev['free']))
  395.         def pixbuf_data_func(column, cell, model, iterator):
  396.             if model[iterator][1] == self.MAYBE:
  397.                 cell.set_property('stock-id', gtk.STOCK_DIALOG_WARNING)
  398.             elif model[iterator][1] == self.NO:
  399.                 # TODO: Implement disabled rows as a replacement?
  400.                 cell.set_property('stock-id', gtk.STOCK_DIALOG_ERROR)
  401.             else:
  402.                 dev = self.backend.devices[model[iterator][0]]
  403.                 drive = self.get_gnome_drive( dev['udi'] )
  404.                 if drive:
  405.                     cell.set_property('icon-name', drive.get_icon())
  406.                 else:
  407.                     cell.set_property('stock-id', None)
  408.  
  409.         list_store = gtk.ListStore(str, int)
  410.         self.dest_treeview.set_model(list_store)
  411.  
  412.         column_name = gtk.TreeViewColumn()
  413.         column_name.set_title(_('Device'))
  414.         cell_name = gtk.CellRendererText()
  415.         cell_pixbuf = gtk.CellRendererPixbuf()
  416.         column_name.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE)
  417.         column_name.pack_start(cell_pixbuf, expand=False)
  418.         column_name.pack_start(cell_name, expand=True)
  419.         self.dest_treeview.append_column(column_name)
  420.         column_name.set_cell_data_func(cell_name, column_data_func, 0)
  421.         column_name.set_cell_data_func(cell_pixbuf, pixbuf_data_func)
  422.         
  423.         cell_name = gtk.CellRendererText()
  424.         column_name = gtk.TreeViewColumn(_('Label'), cell_name)
  425.         column_name.set_cell_data_func(cell_name, column_data_func, 1)
  426.         column_name.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE)
  427.         self.dest_treeview.append_column(column_name)
  428.  
  429.         cell_capacity = gtk.CellRendererText()
  430.         column_name = gtk.TreeViewColumn(_('Capacity'), cell_capacity)
  431.         column_name.set_cell_data_func(cell_capacity, column_data_func, 2)
  432.         column_name.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE)
  433.         self.dest_treeview.append_column(column_name)
  434.  
  435.         cell_free = gtk.CellRendererText()
  436.         column_name = gtk.TreeViewColumn(_('Free Space'), cell_free)
  437.         column_name.set_cell_data_func(cell_free, column_data_func, 3)
  438.         column_name.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE)
  439.         self.dest_treeview.append_column(column_name)
  440.  
  441.         selection = self.dest_treeview.get_selection()
  442.         selection.connect('changed', self.dest_selection_changed)
  443.  
  444.     def install(self, widget):
  445.         self.backend.log('Installing...')
  446.         self.window.hide()
  447.         starting_up = _('Starting up')
  448.         self.progress_title.set_markup('<big><b>' + starting_up + '</b></big>')
  449.         self.progress_info.set_text('')
  450.         self.install_window.show()
  451.         m, i = self.source_treeview.get_selection().get_selected()
  452.         if not i:
  453.             self.failed(_('Install button pressed but there was no source selected.'))
  454.         val = m[i][0]
  455.         self.backend.log('Source CD: %s' % val)
  456.         self.backend.set_install_source(val)
  457.         m, i = self.dest_treeview.get_selection().get_selected()
  458.         if not i:
  459.             self.failed(_('Install button pressed but there was no destination selected.'))
  460.         val = m[i][0]
  461.         self.backend.log('Destination disk: %s' % val)
  462.         self.backend.set_install_target(val)
  463.         if self.persist_enabled.get_active():
  464.             val = int(self.persist_value.get_value())
  465.         else:
  466.             val = 0
  467.         self.backend.log('Persistence size: %d B' % val)
  468.         self.backend.set_persistence_size(val)
  469.         self.backend.install_bootloader()
  470.         try:
  471.             self.backend.copy_files()
  472.         except Exception, e:
  473.             self.failed(str(e))
  474.  
  475.     def progress(self, val, desc):
  476.         self.progress_info.set_text(_('%d%% complete') % val)
  477.         self.progress_bar.set_fraction(val / 100.0)
  478.         self.progress_title.set_markup('<big><b>' + desc + '</b></big>')
  479.  
  480. def format_size(size):
  481.     """Format a partition size."""
  482.     # Taken from ubiquity's ubiquity/misc.py
  483.     if size < 1024:
  484.         unit = 'B'
  485.         factor = 1
  486.     elif size < 1024 * 1024:
  487.         unit = 'kB'
  488.         factor = 1024
  489.     elif size < 1024 * 1024 * 1024:
  490.         unit = 'MB'
  491.         factor = 1024 * 1024
  492.     elif size < 1024 * 1024 * 1024 * 1024:
  493.         unit = 'GB'
  494.         factor = 1024 * 1024 * 1024
  495.     else:
  496.         unit = 'TB'
  497.         factor = 1024 * 1024 * 1024 * 1024
  498.     return '%.1f %s' % (float(size) / factor, unit)
  499.  
  500. # vim: set ai et sts=4 tabstop=4 sw=4:
  501.